package com.hellprototypes.hellwatch.assistant;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.util.Log;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;

public class AssistantService extends Service {
    private final static String TAG = "HellWatch-Service";

    private final static String  SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
    private final static  int  BLE_MAX_MSG_LEN = 20;

    // UUIDs for UAT service and associated characteristics.
    public static UUID UART_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
    public static UUID TX_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
    public static UUID RX_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
    // UUID for the BTLE client characteristic which is necessary for notifications.
    public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    // BTLE state
    private BluetoothAdapter adapter;
    private BluetoothGatt gatt;
    private BluetoothGattCharacteristic tx;
    private BluetoothGattCharacteristic rx;

    //private
    private Handler mScanhandler;
    private NotificationManager mNotificationManager;
    private AssistantServiceReceiver mAssistantServiceReceiver;
    private LocationManager mLocationManager;

    private int mConnectionState = STATE_DISCONNECTED;
    private static final int STATE_DISCONNECTED = 0;
    //private static final int STATE_CONNECTING = 1;
    private static final int STATE_CONNECTED = 2;

    @Override
    public void onCreate() {
        super.onCreate();
        adapter = null;
        mScanhandler = new Handler();

        //Notification
        mNotificationManager = (NotificationManager) getSystemService( NOTIFICATION_SERVICE);
        sendNotification("Started", "");

        //Location
        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        Location location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        updateLocation(location);
        mLocationManager.addGpsStatusListener(gpsStatusListener);
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, locationListener);

        //BroadcastReceiver
        mAssistantServiceReceiver = new AssistantServiceReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
        filter.addAction(SMS_RECEIVED);
        registerReceiver(mAssistantServiceReceiver, filter);
        Log.d(TAG, "* onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "* onStartCommand");
        if (intent != null) {
            Bundle bundle = intent.getExtras();
            if (bundle.containsKey(Common.BUNDLE_KEY_NAME_CMD)) {
                switch (bundle.getInt(Common.BUNDLE_KEY_NAME_CMD)) {
                    case Common.SERVICE_CMD_INIT:
                        if(adapter == null) {
                            if(!getBLAdapter()) {
                                //mScanhandler.postDelayed(scanRunnable, 1000);
                                SendMessageToAssistant("Wait for enable Bluetooth ...");
                            } else {
                                adapter.startLeScan(scanCallback);
                                SendMessageToAssistant("Start device scanning ...");
                            }
                        }
                        break;
                    case Common.SERVICE_CMD_BEEP:
                        //sendMessageToBLE("beep");
                        sendMessageToBLE("beep与显示测试,手机可以发送文本在OLED屏上显示，最多32个汉字。");
                        break;
                    case Common.SERVICE_CMD_START_SCAN:
                        if(adapter != null) {
                            adapter.startLeScan(scanCallback);
                            SendMessageToAssistant("Start device scanning ...");
                        }
                        break;
                    default:
                        Log.d(TAG, "\tonStartCommand : unknown command");
                        break;
                }
            } else
            if(bundle.containsKey(Common.BUNDLE_KEY_IR_SEND)) {
                byte [] ir_code = bundle.getByteArray(Common.BUNDLE_KEY_IR_SEND);
                sendByteArrayToBLE(ir_code);
                SystemClock.sleep(100);
            }
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (gatt != null) {
            // For better reliability be careful to disconnect and close the connection.
            gatt.disconnect();
            gatt.close();
            gatt = null;
        }
        if( adapter.getBondedDevices().size() == 0) {
            adapter.disable();//Disable default BL adapter
        }
        mScanhandler.removeCallbacks(scanRunnable);
        mNotificationManager.cancelAll();
        unregisterReceiver(mAssistantServiceReceiver);
        mLocationManager.removeUpdates(locationListener);
        super.onDestroy();
        Log.d(TAG, "* onDestroy");
    }

    public class AssistantServiceReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if(action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
                String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
                TelephonyManager telephony =(TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
                if(telephony.getCallState() == TelephonyManager.CALL_STATE_RINGING) {
                    String callmsg = "来电:\n" + phoneNumber;
                    SendMessageToAssistant(callmsg);
                    sendMessageToBLE(callmsg);
                }
            } else
            if (action.equals(SMS_RECEIVED)) {
                Bundle extras = intent.getExtras();
                if (extras != null) {
                    Object[] pdus = (Object[]) extras.get("pdus");
                    if(pdus.length > 0) {
                        android.telephony.SmsMessage message = android.telephony.SmsMessage.createFromPdu((byte[]) pdus[0]);
                        String fromAddress = message.getOriginatingAddress();
                        String msgBody = message.getMessageBody();
                        sendMessageToBLE(fromAddress + ":" + msgBody);
                    }
                }
            }
        }
    }

    public void sendNotification(String ContentText, String SubText) {
        Intent intent = new Intent(this, HW_Assistant.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

        Notification.Builder builder = new Notification.Builder(this);
        builder.setSmallIcon(R.drawable.ic_launcher);
        builder.setContentIntent(pendingIntent);
        //builder.setAutoCancel(false);
        builder.setOngoing(true);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher));

        builder.setContentTitle("Hell Watch Assistant");
        builder.setContentText(ContentText);
        builder.setSubText(SubText);

        mNotificationManager.notify(0, builder.build());
    }

    GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener() {
        public void onGpsStatusChanged(int event) {
            switch (event) {
                case GpsStatus.GPS_EVENT_FIRST_FIX:
                    SendMessageToAssistant( "第一次定位");
                    break;
                case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
                    /*
                    //SendMessageToAssistant("卫星状态改变");
                    GpsStatus gpsStatus=mLocationManager.getGpsStatus(null);
                    int maxSatellites = gpsStatus.getMaxSatellites();
                    Iterator<GpsSatellite> iters = gpsStatus.getSatellites().iterator();
                    int count = 0;
                    while (iters.hasNext() && count <= maxSatellites) {
                        GpsSatellite s = iters.next();
                        count++;
                    }
                    SendMessageToAssistant("搜索到："+count+"颗卫星");
                    */
                    break;
                case GpsStatus.GPS_EVENT_STARTED:
                    SendMessageToAssistant("定位启动");
                    break;
                case GpsStatus.GPS_EVENT_STOPPED:
                    SendMessageToAssistant("定位结束");
                    break;
            }
        };
    };

    private LocationListener locationListener=new LocationListener() {
        public void onLocationChanged(Location location) {
            updateLocation(location);
        }
        public void onStatusChanged(String provider, int status, Bundle extras) {
            switch (status) {
                case LocationProvider.AVAILABLE:
                    SendMessageToAssistant("当前GPS状态为可见状态");
                    break;
                case LocationProvider.OUT_OF_SERVICE:
                    SendMessageToAssistant("当前GPS状态为服务区外状态");
                    break;
                case LocationProvider.TEMPORARILY_UNAVAILABLE:
                    SendMessageToAssistant("当前GPS状态为暂停服务状态");
                    break;
            }
        }
        public void onProviderEnabled(String provider) {
            Location location=mLocationManager.getLastKnownLocation(provider);
            updateLocation(location);
        }
        public void onProviderDisabled(String provider) {
            updateLocation(null);
        }
    };

    private void updateLocation(Location location){
        if(location!=null){
            SendMessageToAssistant("==== GPS信息 ====\n经度：" + String.valueOf(location.getLongitude()));
            SendMessageToAssistant("\n纬度：" + String.valueOf(location.getLatitude()));
            SendMessageToAssistant("\n速度：" + String.valueOf(location.getSpeed() + "m/s"));
        }else{
            //SendMessageToAssistant("定位无效");
        }
    }

    // Main BTLE device callback where much of the logic occurs.
    private BluetoothGattCallback callback = new BluetoothGattCallback() {
        // Called whenever the device connection state changes, i.e. from disconnected to connected.
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (newState == BluetoothGatt.STATE_CONNECTED) {
                SendMessageToAssistant("Connected!");
                sendNotification("Device connected", "");
                mConnectionState = STATE_CONNECTED;
                // Discover services.
                if (!gatt.discoverServices()) {
                    SendMessageToAssistant("Failed to start discovering services!");
                }
                String name = gatt.getDevice().getName();
                SendMessageToAssistant("Device Name: [ " + name + " ]");
            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                SendMessageToAssistant("Disconnected!");
                sendNotification("Device disconnected", "Scanning ...");
                mConnectionState = STATE_DISCONNECTED;
                mScanhandler.postDelayed(scanRunnable, 1000);
            } else {
                SendMessageToAssistant("Connection state changed.  New state: " + newState);
            }
        }

        // Called when services have been discovered on the remote device.
        // It seems to be necessary to wait for this discovery to occur before
        // manipulating any services or characteristics.
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                SendMessageToAssistant("Service discovery completed!");
            } else {
                SendMessageToAssistant("Service discovery failed with status: " + status);
                return;
            }
            // Save reference to each characteristic.
            tx = gatt.getService(UART_UUID).getCharacteristic(TX_UUID);
            rx = gatt.getService(UART_UUID).getCharacteristic(RX_UUID);
            // Setup notifications on RX characteristic changes (i.e. data received).
            // First call setCharacteristicNotification to enable notification.
            if (!gatt.setCharacteristicNotification(rx, true)) {
                SendMessageToAssistant("Couldn't set notifications for RX characteristic!");
            }
            // Next update the RX characteristic's client descriptor to enable notifications.
            if (rx.getDescriptor(CLIENT_UUID) != null) {
                BluetoothGattDescriptor desc = rx.getDescriptor(CLIENT_UUID);
                desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                if (!gatt.writeDescriptor(desc)) {
                    SendMessageToAssistant("Couldn't write RX client descriptor value!");
                }
            } else {
                SendMessageToAssistant("Couldn't get RX client descriptor!");
            }
        }

        // Called when a remote characteristic changes (like the RX characteristic).
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            String message = "";
            String key = Common.BUNDLE_KEY_NAME_MSG;
            byte [] rawvalue = characteristic.getValue();
            if(rawvalue.length < 1) {
                return;
            }
            byte [] value;
            if ((rawvalue[0] >= '0') && (rawvalue[0] <= '9')) {//For Sport HW app
                if(rawvalue[0] == '0') {
                    key = Common.BUNDLE_KEY_NAME_STEP;
                } else if(rawvalue[0] == '2') {
                    key = Common.BUNDLE_KEY_NAME_NOTE;
                }
                value = new byte[rawvalue.length-1];
                System.arraycopy(rawvalue, 1, value, 0, value.length);
            } else if(rawvalue[0] == (byte)0x80) {//For IRremote HW app
                value = new byte[rawvalue.length-1];
                System.arraycopy(rawvalue, 1, value, 0, value.length);
                SendDataToIRremote(value);
                SendMessageToAssistant("IR decode data received:" + Common.byte2hex(value));
                return;
            } else {
                value = rawvalue;
                message = Common.byte2hex(value) + ": ";

                //Play notification sound
                Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
                RingtoneManager.getRingtone(getApplicationContext(), notification).play();
            }

            try {
                message += new String(value,"gb2312");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            SendDataToAssistant(key, message);
        }
    };

    // BTLE device scanning callback.
    private BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() {
        // Called when a device is found.
        @Override
        public void onLeScan(BluetoothDevice bluetoothDevice, int RSSI, byte[] bytes) {
            SendMessageToAssistant("# Device: " + bluetoothDevice.getAddress() + " RSSI:" + RSSI);
            // Check if the device has the UART service.
            if (parseUUIDs(bytes).contains(UART_UUID)) {
                // Found a device, stop the scan.
                adapter.stopLeScan(scanCallback);
                SendMessageToAssistant("Found UART service!");
                // Connect to the device.
                // Control flow will now go to the callback functions when BTLE events occur.
                gatt = bluetoothDevice.connectGatt(getApplicationContext(), false, callback);
            }
        }
    };

    // Filtering by custom UUID is broken in Android 4.3 and 4.4, see:
    //   http://stackoverflow.com/questions/18019161/startlescan-with-128-bit-uuids-doesnt-work-on-native-android-ble-implementation?noredirect=1#comment27879874_18019161
    // This is a workaround function from the SO thread to manually parse advertisement data.
    private List<UUID> parseUUIDs(final byte[] advertisedData) {
        List<UUID> uuids = new ArrayList<UUID>();

        int offset = 0;
        while (offset < (advertisedData.length - 2)) {
            int len = advertisedData[offset++];
            if (len == 0)
                break;

            int type = advertisedData[offset++];
            switch (type) {
                case 0x02: // Partial list of 16-bit UUIDs
                case 0x03: // Complete list of 16-bit UUIDs
                    while (len > 1) {
                        int uuid16 = advertisedData[offset++];
                        uuid16 += (advertisedData[offset++] << 8);
                        len -= 2;
                        uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
                    }
                    break;
                case 0x06:// Partial list of 128-bit UUIDs
                case 0x07:// Complete list of 128-bit UUIDs
                    // Loop through the advertised 128-bit UUID's.
                    while (len >= 16) {
                        try {
                            // Wrap the advertised bits and order them.
                            ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
                            long mostSignificantBit = buffer.getLong();
                            long leastSignificantBit = buffer.getLong();
                            uuids.add(new UUID(leastSignificantBit,
                                    mostSignificantBit));
                        } catch (IndexOutOfBoundsException e) {
                            // Defensive programming.
                            //Log.e(LOG_TAG, e.toString());
                            //continue;
                        } finally {
                            // Move the offset to read the next uuid.
                            offset += 15;
                            len -= 16;
                        }
                    }
                    break;
                default:
                    offset += (len - 1);
                    break;
            }
        }
        return uuids;
    }

    //Format: index(1B) + message(19B MAX)
    public void sendMessageToBLE(String message) {
        if (gatt == null || tx == null || message == null || message.isEmpty()) {
            // Do nothing if there is no device or message to send.
            return;
        }
        // Update TX characteristic value.  Note the setValue overload that takes a byte array must be used.
        byte[] msg_bytes = message.getBytes(Charset.forName("gb2312"));
        SendMessageToAssistant("msg_bytes: [ " + Common.byte2hex(msg_bytes) + " ]");
        byte sequence = 0;//0:reset display buffer, 1:add to display buffer
        int index = 0;
        while( index < msg_bytes.length ) {
            int cpy_len = msg_bytes.length - index;
            if(cpy_len > (BLE_MAX_MSG_LEN-1)) {
                cpy_len = BLE_MAX_MSG_LEN-1;
            }
            byte[] value = new byte[cpy_len + 1];
            value[0] = sequence;
            System.arraycopy(msg_bytes, index, value, 1, cpy_len);
            index += cpy_len;
            tx.setValue(value);
            if (gatt.writeCharacteristic(tx)) {
                SendMessageToAssistant("Sent: [ " + Common.byte2hex(value) + " ]");
                SystemClock.sleep(50);
            } else {
                SendMessageToAssistant("Couldn't write TX characteristic!");
                break;
            }
            sequence = 1;
        }
    }

    public void sendByteArrayToBLE(byte [] bytes) {
        if (gatt == null || tx == null || bytes == null || (bytes.length==0) ||(bytes.length > 20)) {
            return;
        }
        tx.setValue(bytes);
        if (gatt.writeCharacteristic(tx)) {
            SendMessageToAssistant("Sent: [ " + Common.byte2hex(bytes) + " ]");
        } else {
            Log.d(TAG, "Couldn't write TX characteristic!");
        }
    }

    public boolean getBLAdapter() {
        ////adapter = BluetoothAdapter.getDefaultAdapter();
        // Initializes Bluetooth adapter.
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        adapter = bluetoothManager.getAdapter();

        // Ensures Bluetooth is available on the device and it is enabled. If not,
        // displays a dialog requesting user permission to enable Bluetooth.
        if (adapter == null || !adapter.isEnabled()) {
            SendCommandToAssistant(Common.SERVICE_CMD_REQ_EN_BT);
            return false;
        }
        return true;
    }

    public void SendCommandToAssistant(int command) {
        Intent intent = new Intent(Common.HWA_ACTION);
        intent.putExtra(Common.BUNDLE_KEY_NAME_CMD, command);
        sendBroadcast(intent);
    }

    public void SendDataToAssistant(String key, String Data) {
        Intent intent = new Intent(Common.HWA_ACTION);
        intent.putExtra(key, Data);
        sendBroadcast(intent);
    }

    public void SendDataToIRremote(byte[] Data) {
        Intent intent = new Intent(Common.HWIR_ACTION);
        intent.putExtra(Common.BUNDLE_KEY_IR_DATA, Data);
        sendBroadcast(intent);
    }

    public void SendMessageToAssistant(String msg) {
        SendDataToAssistant(Common.BUNDLE_KEY_NAME_MSG, msg);
    }

    private Runnable scanRunnable = new Runnable( ) {
        public void run ( ) {
            //Log.d(TAG, "* scanRunnable run");
            if(mConnectionState == STATE_DISCONNECTED) {
                if(adapter != null) {
                    if(gatt != null) {
                        gatt.disconnect();
                        gatt.close();
                        gatt = null;
                    }
                    adapter.startLeScan(scanCallback);
                    SendMessageToAssistant("Retart device scanning ...");
                }
                //Log.d(TAG, "* TimerTask STATE_DISCONNECTED");
            }
        }
    };

    @Override
    public IBinder onBind(Intent arg0) {
        Log.d(TAG, "* onBind()");
        return null;
    }
}
